// @ts-check
import { resolve, dirname } from "path"

/** @typedef {string} PageSlotComponent */

/** @typedef {{[pluginPageSlot: string]: PageSlotComponent}} PageSlots */

/**
 * @typedef {object} PluginItem
 * @property {string} [export]
 * @property {{[pluginPageName: string]: PageSlots}} [genericsImplementation]
 * */

/**
 * @typedef {object} MovePluginExportOptions
 * @property {object} inject
 * @property {(root: string) => boolean} inject.ignores
 * @property {Array<{path: string; data: any;}>} inject.injections
 */

/**
 * 处理app.config.ts中插件export和genericsImplementation配置
 * @param {import("@tarojs/service").IPluginContext} ctx 插件上下文
 * @param {MovePluginExportOptions} opts 插件配置
 */
export default function movePluginExport(ctx, opts) {
  ctx.onBuildComplete(() => {
    const { outputPath, sourcePath } = ctx.paths
    const appJsonPath = resolve(outputPath, "app.json")
    const appJsonObj = ctx.helper.fs.readJsonSync(appJsonPath)
    try {
      const { plugins, subpackages } = appJsonObj
      handlePlugins(plugins, "")
      if (subpackages && subpackages.length > 0) {
        subpackages.forEach(subpackage => {
          handlePlugins(subpackage.plugins, subpackage.root)
        })
      }
      console.log(ctx.helper.chalk.green(`[movePluginExport]: 插件export处理完成!`))
    } catch (e) {
      console.log(ctx.helper.chalk.red(`[movePluginExport]: 插件export处理发生错误!`))
      console.error(e)
    }
    /**
     * @param {{[pluginName: string]: PluginItem}} plugins
     * @param {string} root
     */
    function handlePlugins(plugins, root) {
      if (plugins && Object.keys(plugins).length > 0) {
        Object.keys(plugins).forEach(pluginName => {
          handlePluginExport(plugins[pluginName], root)
          handlePluginGenericsImplementation(plugins[pluginName], root)
        })
        handleInject(root)
      }
    }
    /**
     * @param {PluginItem} pluginItem
     * @param {string} root
     */
    function handlePluginExport(pluginItem, root) {
      if (!pluginItem.export) return
      const exportFromPath = resolve(sourcePath, root, pluginItem.export)
      if (ctx.helper.fs.pathExistsSync(exportFromPath)) {
        const exportToPath = resolve(outputPath, root, pluginItem.export)
        ctx.helper.fs.copySync(exportFromPath, exportToPath, { overwrite: true, recursive: true })
      }
    }
    /**
     * @param {PluginItem} pluginItem
     * @param {string} root
     */
    function handlePluginGenericsImplementation(pluginItem, root) {
      /** @type {string[]} */
      let pluginPageNames = []
      if (
        !pluginItem.genericsImplementation ||
        (pluginPageNames = Object.keys(pluginItem.genericsImplementation)).length === 0
      )
        return
      pluginPageNames.forEach(function (pluginPageName) {
        // @ts-ignore
        const pluginPageSlots = pluginItem.genericsImplementation[pluginPageName]
        Object.keys(pluginPageSlots).forEach(function (pluginPageSlot) {
          const slotComponent = dirname(pluginPageSlots[pluginPageSlot])
          const exportFromPath = resolve(sourcePath, root, slotComponent)
          if (ctx.helper.fs.existsSync(exportFromPath)) {
            const exportToPath = resolve(outputPath, root, slotComponent)
            ctx.helper.fs.copySync(exportFromPath, exportToPath, {
              overwrite: true,
              recursive: true
            })
          }
        })
      })
    }
    /**
     * @param {string} root
     */
    function handleInject(root) {
      if (!opts.inject || !opts.inject.injections || opts.inject.injections.length === 0) return
      if (typeof opts.inject.ignores === "function" && opts.inject.ignores(root)) return
      for (const injection of opts.inject.injections) {
        const fromPath = resolve(sourcePath, root, injection.path)
        if (ctx.helper.fs.existsSync(fromPath)) {
          ;(async function () {
            const ejs = (await import("ejs")).default
            const content = await ejs.renderFile(fromPath, injection.data, { async: true })
            const toPath = resolve(outputPath, root, injection.path)
            ctx.helper.fs.writeFileSync(toPath, content)
          })()
        } else {
          console.log(
            ctx.helper.chalk.yellow(
              `[movePluginExport] warn: ${fromPath} not found and was ignored.`
            )
          )
        }
      }
    }
  })
}
